package br.elsa.survey.dao;

import java.io.Serializable;
import java.util.List;

import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;

import br.elsa.survey.dao.exceptions.DataNotFoundException;

/**
 * DAO Base class that provides basic database access functionalities.
 * 
 * @author diego
 * @since 2010-10-22
 * 
 */
public abstract class Dao<T> implements Serializable {

	private static final long serialVersionUID = 2560708703585001321L;

	@PersistenceContext
	protected EntityManager entityManager;

	/**
	 * Default constructor
	 */
	public Dao() {

	}

	/**
	 * Constructor used by test classes to inject mocked dependencies
	 * 
	 * @param entityManager
	 *            Mocked dependency
	 */
	public Dao(EntityManager entityManager) {
		this.entityManager = entityManager;
	}

	/**
	 * Inserts a new entity data into database
	 * 
	 * @param entity
	 *            the entity to be inserted into database
	 */
	@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
	public void insert(T entity) {
		entityManager.persist(entity);
	}

	/**
	 * Retrieves all entries of entities (concerned) from database
	 * 
	 * @return the fetch result
	 * @exception DataNotFoundException
	 *                Thrown if no data is found
	 */
	public List<T> findAll(Class<T> entityClass) throws DataNotFoundException {

		CriteriaBuilder cb = entityManager.getCriteriaBuilder();
		CriteriaQuery<T> criteria = cb.createQuery(entityClass);
		Root<T> entity = criteria.from(entityClass);
		criteria.select(entity);

		List<T> entities = entityManager.createQuery(criteria).getResultList();

		if (entities != null && entities.size() > 0) {
			return entities;
		}

		throw new DataNotFoundException("No entity was found.");
	}

	/**
	 * Deletes the specific entity from database
	 * 
	 * @param entity
	 *            the entity to be deleted
	 */
	@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
	public void delete(T entity) {

		T attachedEntity = entityManager.merge(entity);
		entityManager.remove(attachedEntity);
	}

	/**
	 * Counts the number of entities entries in database
	 * 
	 * @param entityClass
	 *            the entity type
	 * @return the count of entities entries in database
	 */
	public int count(Class<?> entityClass) {

		CriteriaBuilder cb = entityManager.getCriteriaBuilder();
		CriteriaQuery<Long> c = cb.createQuery(Long.class);
		Root<?> f = c.from(entityClass);
		c.select(cb.count(f));

		return entityManager.createQuery(c).getSingleResult().intValue();
	}
}
